package com.sneakxy.mybatis.commons.repository.query.executor;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.binding.MapperMethod.ParamMap;
import org.apache.ibatis.builder.SqlSourceBuilder;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.scripting.xmltags.DynamicSqlSource;
import org.apache.ibatis.scripting.xmltags.MixedSqlNode;
import org.apache.ibatis.scripting.xmltags.SqlNode;
import org.apache.ibatis.scripting.xmltags.TextSqlNode;

import com.sneakxy.mybatis.commons.orm.MybatisEntityManager;
import com.sneakxy.mybatis.commons.orm.generation.GenerationManager;
import com.sneakxy.mybatis.commons.orm.generation.GenerationType;
import com.sneakxy.mybatis.commons.orm.mapping.MybatisColumn;
import com.sneakxy.mybatis.commons.orm.mapping.MybatisColumnSqlNode;
import com.sneakxy.mybatis.commons.repository.query.parser.MybatisPartTree;
import com.sneakxy.mybatis.commons.utils.MybatisUtils;
import com.sneakxy.mybatis.commons.utils.SnowflakeIdGenerator;

/**
 * @author 潜行的青衣
 */
public class SaveRepositoryExecutor<T> extends AbstractRepositoryExecutor<T> {

	private String insertIdStatement;
	
	private String updateStatement;
	
	private SnowflakeIdGenerator snowflakeIdGenerator;
	
	/**
	 * @param entityManager
	 * @param loader
	 * @param method
	 * @param type
	 */
	public SaveRepositoryExecutor(MybatisEntityManager entityManager, SnowflakeIdGenerator snowflakeIdGenerator, Class<?> loader, String method, Class<?>...type) {
		super(entityManager, loader, method, type);
		this.snowflakeIdGenerator = snowflakeIdGenerator;
	}
	
	/**
	 * @param entityManager
	 */
	public SaveRepositoryExecutor(MybatisEntityManager entityManager, SnowflakeIdGenerator snowflakeIdGenerator, Method method) {
		super(entityManager, method);
		this.snowflakeIdGenerator = snowflakeIdGenerator;
	}

	/* (non-Javadoc)
	 * @see com.sneakxy.mybatis.commons.repository.query.executor.RepositoryExecutor#execute(java.lang.Object[])
	 */
	@SuppressWarnings("unchecked")
	@Override
	public T execute(Object... parameters) {
		Object entity = parameters[0];
		String table_name = (String) parameters[1];
		boolean isNew = getEntityManager().getEntityInformation().isNew(entity);
		if(isNew) {
			//是否符合主键
			boolean isComposite = getEntityManager().getEntityInformation().isComposite();
			if(!isComposite) {
				Object id = getEntityManager().getEntityInformation().getId(entity);
				//需要自动生成ID
				if(id == null) {
					GenerationType generationType = getEntityManager().getGenerationManager().getGenerationType();
					try {
						if(generationType == GenerationType.UUID) {
							getEntityManager().getEntityInformation().setId(entity, MybatisUtils.getUUID());
						} else if (generationType == GenerationType.SNOWFLAKE) {
							//雪花算法
							getEntityManager().getEntityInformation().setId(entity, getSnowflakeIdGenerator().generateId());
						}
					} catch (IllegalAccessException e) {}
				}
			}
			this.defaultInsert(entity, table_name);
		} else {
			this.defaultUpdate(entity, table_name);
		}
		return (T) entity;
	}

	/* (non-Javadoc)
	 * @see com.sneakxy.mybatis.commons.repository.query.executor.AbstractRepositoryExecutor#onBuildStatment()
	 */
	@Override
	protected void onBuildStatment() {
		this.insertIdStatement = this.getEntityManager().getNamespacedId("_defaultInsertId");
		this.updateStatement = this.getEntityManager().getNamespacedId("_defaultUpdate");
		this.buildInsertStatement();
		this.buildUpdateStatement();
	}

	@SuppressWarnings("unchecked")
	protected Object defaultInsert(Object... parameters) {
		Object entity = parameters[0];
		Map<String, Object> parameterObject = (Map<String, Object>) this.createParameterObject(parameters, false);
		this.getEntityManager().getSqlSessionTemplate().insert(this.getStatement(), parameterObject);
		if(this.getEntityManager().getGenerationManager().hasIdStrategy()) {
			Long id = this.getEntityManager().getSqlSessionTemplate().selectOne(this.insertIdStatement);
			if(id != null) {
				try {
					this.getEntityManager().getEntityInformation().setId(entity, id);
				} catch (IllegalAccessException e) {
					e.printStackTrace();
				}
			}
		}
		return entity;
	}
	
	@SuppressWarnings("unchecked")
	protected Object defaultUpdate(Object... parameters) {
		Object entity = parameters[0];
		Map<String, Object> parameterObject = (Map<String, Object>) this.createParameterObject(parameters, false);
		this.getEntityManager().getSqlSessionTemplate().update(this.updateStatement, parameterObject);
		return entity;
	}
	
	protected void buildInsertStatement() {
		GenerationManager generationManager = this.getEntityManager().getGenerationManager();
		boolean hasStrategy = generationManager.hasIdStrategy();
		StringBuilder sqlBuilder = new StringBuilder();
		StringBuilder valuesSqlBuilder = new StringBuilder();
		sqlBuilder.append("INSERT INTO ${").append(MybatisPartTree.GENERIC_NAME_PREFIX).append("2} (");
		valuesSqlBuilder.append("VALUES(");
		List<MybatisColumn> columns = this.getEntityManager().getEntityColumns(StringUtils.join(MybatisPartTree.GENERIC_NAME_PREFIX, "1"));
		Iterator<MybatisColumn> it = columns.iterator();
		MybatisColumn column = null;
		while(it.hasNext()) {
			column = it.next();
			//主键，需要根据策略决定
			if(hasStrategy && column.isPrimary()) {
				this.buildInsertIdStatment(column.getColumn());
				if(generationManager.needSequenceIdColumnSql()) {
					sqlBuilder.append(generationManager.formatColumnName(column.getColumn()));
					valuesSqlBuilder.append(generationManager.getSequenceIdColumnValue());
					if(it.hasNext()) {
						sqlBuilder.append(",");
						valuesSqlBuilder.append(",");
					}
				}
			} else {
				sqlBuilder.append(generationManager.formatColumnName(column.getColumn()));
				valuesSqlBuilder.append("#{").append(column.getPropertyPath()).append("}");
				if(it.hasNext()) {
					sqlBuilder.append(",");
					valuesSqlBuilder.append(",");
				}
			}
		}
		valuesSqlBuilder.append(")");
		sqlBuilder.append(") ").append(valuesSqlBuilder.toString());
		
		String id = this.getStatement();
		String sql = sqlBuilder.toString();
		SqlCommandType sqlCommandType  = SqlCommandType.INSERT;
		
		SqlSource sqlSource = new DynamicSqlSource(this.getEntityManager().getConfiguration(), new TextSqlNode(sql));
		this.getEntityManager().addMappedStatement(id, sqlSource, sqlCommandType, ParamMap.class, Integer.class);
	}
	
	protected void buildInsertIdStatment(String idColumn) {
		GenerationManager generationManager = this.getEntityManager().getGenerationManager();
		String id = this.insertIdStatement;
		String sql = generationManager.getSequenceIdSql(idColumn);
		SqlCommandType sqlCommandType  = SqlCommandType.SELECT;
		SqlSourceBuilder builder = new SqlSourceBuilder(this.getEntityManager().getConfiguration());
		SqlSource sqlSource = builder.parse(sql, ParamMap.class, new HashMap<String, Object>());
		this.getEntityManager().addMappedStatement(id, sqlSource, sqlCommandType, ParamMap.class, Long.class);
	}
	
	protected void buildUpdateStatement() {
		GenerationManager generationManager = this.getEntityManager().getGenerationManager();
		StringBuilder sqlBuilder = new StringBuilder();
		StringBuilder whereBuilder = new StringBuilder();
		List<SqlNode> contents = new ArrayList<>();
		List<MybatisColumn> columns = this.getEntityManager().getEntityColumns(StringUtils.join(MybatisPartTree.GENERIC_NAME_PREFIX, "1"));
		Iterator<MybatisColumn> it = columns.iterator();
		MybatisColumn column = null;
		sqlBuilder.append("UPDATE ${").append(MybatisPartTree.GENERIC_NAME_PREFIX).append("2} SET ");
		contents.add(new TextSqlNode(sqlBuilder.toString()));
		whereBuilder.append(" WHERE ");
		while(it.hasNext()) {
			column = it.next();
			if(column.isPrimary()) {
				whereBuilder.append(generationManager.formatColumnName(column.getColumn())).append("=");
				whereBuilder.append("#{").append(column.getPropertyPath()).append("} AND ");
			}
		}
		contents.add(new MybatisColumnSqlNode(generationManager, columns, ","));
		whereBuilder.delete(whereBuilder.length() - 4, whereBuilder.length());
		contents.add(new TextSqlNode(whereBuilder.toString()));
		
		String id = this.updateStatement;
		SqlCommandType sqlCommandType  = SqlCommandType.UPDATE;
		SqlSource sqlSource = new DynamicSqlSource(this.getEntityManager().getConfiguration(), new MixedSqlNode(contents));
		this.getEntityManager().addMappedStatement(id, sqlSource, sqlCommandType, ParamMap.class, Integer.class);
	}

	protected SnowflakeIdGenerator getSnowflakeIdGenerator() {
		return snowflakeIdGenerator;
	}

}
